#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
Usually, Consumer means request-consumer(reactor),
response-consumer should be another being.
response-consumer, standing on the contrary-size of request-consumer,
stands with request-producer usually.
response-consumer verify the response for request-producer.

'''

'''
PACKET SPECIFICATION:
    packet ::= route_slice separator data_slice
    separator ::= '\t'

    route_slice         ::= action_field service_field id_field

    action_field        ::= 'action:' action_name
    action_name         ::= 'REQUEST' | 'RESPONSE' | 'FORWARD'

    service_field       ::= 'service:' service_name
    service_name        ::= letter (letter | digit)*

    id_field            ::= packet_id_field [respone_id_field]
    packet_id_field     ::= 'packid:' decimalinteger
    response_id_field   ::= 'respid:' decimalinteger

    data_slice ::= 'body:' data
    data       ::= base64_encode_string

    identifier ::=  (letter|"_") (letter | digit | "_")*
    letter     ::=  lowercase | uppercase
    lowercase  ::=  "a"..."z"
    uppercase  ::=  "A"..."Z"
    digit      ::=  "0"..."9"

    decimalinteger ::=  nonzerodigit digit* | "0"
    nonzerodigit   ::=  "1"..."9"

'''

import sys
import asyncore, asynchat, socket, threading, time
from base64 import b64encode

HOST = "127.0.0.1"
PORT = 54344

def DEBUG(format, *args) : print format % args

#######################################################################

class   Packet(object)  :
    def __init__(self) :
        self.action = None
        self.service = None
        self.body = None
        self.packid = 0
        self.respid = 0
        # __init__()

    def isComplete(self) :
        if not self.action or not self.service or not self.body :
            return  False
        return  True

    def isRequest(self) :
        if 'REQUEST' == self.action : return True
        return  False
    # class Packet

def depack(record) :
    '''decode packet record string into Packet struct'''
    field_separator = '\t'
    kv_separator = ':'
    packet = Packet()
    fields = record.split(field_separator)
    for f in fields :
        (key, value) = f.split(kv_separator, 1)
        if not value : continue
        if 'action' == key :
            packet.action = value
        elif 'service' == key :
            packet.service = value
        elif 'packid' == key :
            packet.packid = int(value)
        elif 'respid' == key :
            packet.respid = int(value)
        elif 'body' == key :
            packet.body = value

    if not packet.isComplete() : return None
    return  packet
    # depack()

def enpack(packet) :
    '''encode Packet struct into packet record string'''
    record = "action:%s\tservice:%s\tpackid:%d\tbody:%s" % (packet.action, packet.service, packet.packid, packet.body)
    if packet.respid > 0 :
        record += "\trespid:%d" % packet.respid
    return  record
    # enpack

#######################################################################

class echo_client(asynchat.async_chat):

    MIN_PACKET_ID = 1
    MAX_PACKET_ID = 63335
    SERVICE = 'ECHO'

    def __init__(self, producer):
        asynchat.async_chat.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((HOST, PORT))
        self.set_terminator('\n')
        self.buffer = ""

        self.producer = producer
        self.__packet_id = self.MAX_PACKET_ID
        self.__table_packet_out = {}
        # __init__()

    def handle_connect(self):
        print "CLIENT Connecting ..."

    def collect_incoming_data(self, data):
        DEBUG('CLIENT collect: >>>%s<<<', data)
        self.buffer = self.buffer + data

    def found_terminator(self):
        print "CLIENT Received:", self.buffer
        record = self.buffer
        self.buffer = ""

        packet = depack(record)
        if packet is None : return
        if packet.respid in self.__table_packet_out :
            del self.__table_packet_out[ packet.respid ]
        # found_terminator()

    def run(self) :
        try :
            while True :
                if (len(self.producer_fifo) < 1) and (len(self.__table_packet_out) < 1) :
                    if self.producer.empty() :
                        DEBUG("Game Over.")
                        break
                    msg = self.producer.more()
                    if msg :
                        packet, packid = self.enpackRequest(self.SERVICE, msg)
                        self.push(packet + '\n')
                        self.__table_packet_out[packid] = packet
                asyncore.loop(timeout=1.0, count=1)
        except :
            DEBUG( "CLIENT interrupt : %s", `sys.exc_info()[1]` )
            pass
        # run()

    def enpackRequest(self, service, msg) :
        if not msg : return None
        packid = self.getPacketID()
        packet = "action:%s\tservice:%s\tpackid:%d\tbody:%s" % ('REQUEST', self.SERVICE, packid, b64encode(msg))
        return (packet, packid)
        # enpackRequest()

    def getPacketID(self) :
        if self.MAX_PACKET_ID == self.__packet_id :
            self.__packet_id = self.MIN_PACKET_ID
        else :
            self.__packet_id += 1

        return  self.__packet_id
        # getRequestID()

class   Producer(object) :
    def empty(self) :
        return  True
        # empty()

    def more(self) :
        raise NotImplementedError
        # more()
    # class Producer

class   SimpleProducer(Producer) :
    def __init__(self, maximum) :
        self.maximum = maximum
        self.count = 0
        # __init__()

    def empty(self) :
        return  self.count >= self.maximum
        # empty()

    def more(self) :
        if self.empty() : return None
        self.count += 1
        msg = '#' + str(self.count) + ' hello, world.'
        return msg
        # more()
    # class SimpleProducer()

def main():
    producer = SimpleProducer(10)
    c = echo_client(producer)
    c.run()

if __name__ == '__main__' :
    main()
    sys.exit(0)
